﻿using System;
using System.Linq;
using System.Text.RegularExpressions;
using DotNetNuke.Common;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Services.FileSystem;

namespace BrandonHaynes.Providers
    {
    internal static class Extensions
        {
        // Why isn't this defined as a constant in the core?  Ugh.
        private const string ViewFolderPermissionCode = "READ";

        /// <summary>
        /// Given a folder instance, performs the following:
        ///     1) Identify the equivalent folder instance (if any)
        ///     2) Identify the provider associated with that parent instance
        ///     3) Perform an operation on that provider, defined provider->(folder->T)
        /// </summary>
        /// <param name="folder">The child folder instance</param>
        /// <param name="operation">The operation to perform</param>
        public static T ParentPortalOperation<T>(this IFolderInfo folder, Func<FolderProvider, Func<IFolderInfo, T>> operation)
            { return ParentPortalOperation(folder, operation, default(T)); }
        /// <summary>
        /// Given a folder instance, performs the following:
        ///     1) Identify the equivalent folder instance (if any)
        ///     2) Identify the provider associated with that parent instance
        ///     3) Perform an operation on that provider, defined provider->(folder->T)
        /// </summary>
        /// <param name="folder">The child folder instance</param>
        /// <param name="operation">The operation to perform</param>
        /// <param name="defaultValue">A default value if the provider is not found</param>
        public static T ParentPortalOperation<T>(this IFolderInfo folder, Func<FolderProvider, Func<IFolderInfo, T>> operation, T defaultValue)
            {
            var parentFolder = folder.GetParentFolder();
            return parentFolder != null
                ? operation(parentFolder.GetProvider())(parentFolder)
                : defaultValue;
            }

        /// <summary>
        /// Given a file instance, performs the following:
        ///     1) Identify the equivalent file instance (if any)
        ///     2) Identify the provider associated with that parent instance
        ///     3) Perform an operation on that provider, defined provider->(file->T)
        /// </summary>
        /// <param name="file">The child file instance</param>
        /// <param name="operation">The operation to perform</param>
        public static T ParentPortalOperation<T>(this IFileInfo file, Func<FolderProvider, Func<IFileInfo, T>> operation)
            { return ParentPortalOperation(file, operation, default(T)); }
        /// <summary>
        /// Given a file instance, performs the following:
        ///     1) Identify the equivalent file instance (if any)
        ///     2) Identify the provider associated with that parent instance
        ///     3) Perform an operation on that provider, defined provider->(file->T)
        /// </summary>
        /// <param name="file">The child file instance</param>
        /// <param name="operation">The operation to perform</param>
        /// <param name="defaultValue">A default value if the provider is not found</param>
        public static T ParentPortalOperation<T>(this IFileInfo file, Func<FolderProvider, Func<IFileInfo, T>> operation, T defaultValue)
            {
            var parentFile = file.GetParentFile();
            return parentFile != null
                ? operation(parentFile.GetProvider())(parentFile)
                : default(T);
            }

        /// <summary>
        /// Given a folder mapping and file pah instance, performs the following:
        ///     1) Use the folder mapping to identify the child folder instance
        ///     2) Identify the equivalent parent folder mapping instance (if any)
        ///     3) Identify the provider associated with that parent instance
        ///     4) Perform an operation on that provider, defined provider->(folder->T)
        /// </summary>
        /// <param name="folderMapping">The child folder mapping instance</param>
        /// <param name="folderPath">The child folder path</param>
        /// <param name="operation">The operation to perform</param>
        /// <param name="defaultValue">A default value if the provider is not found</param>
        public static T ParentPortalOperation<T>(this FolderMappingInfo folderMapping, string folderPath, Func<FolderProvider, Func<IFolderInfo, T>> operation, T defaultValue)
            {
            var parentFolder = folderMapping.GetParentFolder(folderPath);
            return parentFolder != null
                ? operation(parentFolder.GetProvider())(parentFolder)
                : default(T);
            }


        /// <summary>
        /// Given a folder mapping instance, attempt to determine its associated
        /// parent portal.
        /// </summary>
        /// <param name="folderMapping">The child folder mapping</param>
        /// <returns>The parent PortalInfo instance, if any, otherwise null</returns>
        public static PortalInfo GetParentPortal(this FolderMappingInfo folderMapping)
            { return GetParentPortal(folderMapping.PortalID); }
        /// <summary>
        /// Given a child portalId, attempt to determine its associated
        /// parent portal.
        /// </summary>
        /// <param name="portalId">The child portalId</param>
        /// <returns>The parent PortalInfo instance, if any, otherwise null</returns>
        private static PortalInfo GetParentPortal(int portalId)
            {
            // We assume that the first PortalAlias is the "default" (not a great assumption)
            var defaultPortalAlias = new PortalAliasController().GetPortalAliasByPortalID(portalId).Values.Cast<PortalAliasInfo>().First();
            // Tentatively identiy an alias by removing the last path token
            // "parent/child" -> "parent"
            var candidateAlias = Regex.Replace(defaultPortalAlias.HTTPAlias, "/[^/]+$", String.Empty);
            // Use our tentative alias to find a potential parent portal
            var alias = PortalAliasController.GetPortalAliasInfo(candidateAlias);
            // Return its associated PortalInfo instance, if any, otherwise null
            return alias != null
                ? new PortalController().GetPortal(alias.PortalID)
                : null;
            }

        /// <summary>
        /// Given a folder instance, locate its provider
        /// </summary>
        public static FolderProvider GetProvider(this IFolderInfo folder)
            { return GetProvider(folder.FolderMappingID); }
        /// <summary>
        /// Given a file instance, locate its provider
        /// </summary>
        public static FolderProvider GetProvider(this IFileInfo file)
            { return GetProvider(file.FolderMappingID); }
        /// <summary>
        /// Given a folder mapping identifier, locate its associated provider
        /// </summary>
        private static FolderProvider GetProvider(int folderMappingId)
            {
            var folderMapping = FolderMappingController.Instance.GetFolderMapping(folderMappingId);
            return FolderProvider.Instance(folderMapping.FolderProviderType);
            }


        /// <summary>
        /// Given a child file instance, identify its associated parent file instance
        /// </summary>
        /// <param name="file">The child file instance</param>
        /// <returns>The associated parent file instance, if any</returns>
        public static IFileInfo GetParentFile(this IFileInfo file)
            { return GetParentFile(file.PortalId, file.RelativePath); }
        private static IFileInfo GetParentFile(int portalId, string relativePath)
            {
            var parent = GetParentPortal(portalId);
            return parent != null
                ? FileManager.Instance.GetFile(parent.PortalID, RemoveFirstFolder(relativePath))
                : null;
            }

        /// <summary>
        /// Given a child folder instance, identify its associated parent folder instance
        /// </summary>
        /// <param name="folder">The child folder instance</param>
        /// <returns>The associated parent folder instance, if any</returns>
        public static IFolderInfo GetParentFolder(this IFolderInfo folder)
            { return GetParentFolder(folder.PortalID, folder.FolderPath); }
        /// <summary>
        /// Given a child folder mapping instance and relative path, identify 
        /// its associated parent folderinstance (if any)
        /// </summary>
        /// <param name="folderMapping">The child portal folder mapping</param>
        /// <param name="relativePath">The relative path of the file in the child portal</param>
        /// <returns>The associated parent file instance, if any</returns>
        public static IFolderInfo GetParentFolder(this FolderMappingInfo folderMapping, string relativePath)
            { return GetParentFolder(folderMapping.PortalID, relativePath); }
        private static IFolderInfo GetParentFolder(int portalId, string relativePath)
            {
            var parent = GetParentPortal(portalId);
            return parent != null
                ? FolderManager.Instance.GetFolder(parent.PortalID, RemoveFirstFolder(relativePath))
                : null;
            }

        /// <summary>
        /// Given a folder instance, identify its associated provider mapping
        /// </summary>
        public static FolderMappingInfo GetMapping(this IFolderInfo folder)
            { return FolderMappingController.Instance.GetFolderMapping(folder.FolderMappingID); }

        /// <summary>
        /// Identify whether a folder instance is anonymously-readable
        /// </summary>
        public static bool IsAnonymouslyReadable(this IFolderInfo folder)
            {
            return folder != null
                ? folder.FolderPermissions.ToString(ViewFolderPermissionCode).Contains(Globals.glbRoleAllUsersName)
                : false;
            }



        /// <summary>
        /// Determine if a given path is the root folder of a symbolic link.
        /// We treat the root as special in certain circumstances (e.g. it may 
        /// be renamed)
        /// </summary>
        public static bool IsRootFolderOfSymbolicLink(string path)
            { return path == GetFirstFolder(path); }


        private static string GetFirstFolder(string path)
            { return path.Contains("/") 
                ? String.Concat(path.Remove(path.IndexOf('/')), '/')
                : string.Empty; }
        private static string RemoveFirstFolder(string path)
            { return Regex.Replace(path, "^[^/]+/", string.Empty); }
        }
    }
